﻿using System;
using System.Data;
using System.Configuration;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.Security.Policy;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Security.Cryptography;

namespace net.veekee.common.rules
{
    public class RuleDynamicClass : DynamicClass
    {
        private static Dictionary<String, bool> sourceUpdated = null;
        private static Dictionary<String, List<String>> sourceGlobal = null;
        private static Dictionary<String, List<String>> sourceWhen = null;
        private static Dictionary<String, List<String>> sourceThen = null;

        static RuleDynamicClass()
        {
            RuleDynamicClass.sourceUpdated = new Dictionary<String, bool>();
            RuleDynamicClass.sourceGlobal = new Dictionary<String, List<String>>();
            RuleDynamicClass.sourceWhen = new Dictionary<String, List<String>>();
            RuleDynamicClass.sourceThen = new Dictionary<String, List<String>>();
        }

        private String ruleFileName = null;

        private String entryType = null;
        private String entryName = null;
        private List<String> sourceReferencedGlobal = null;
        private String whenConditionalOperator = null;
        private String entryContextName = null;
        private String entryContextType = null;
        private String regexName = null;
        private String regexExpression = null;
        private RuleDynamicClassCompilationStep compilationStep = RuleDynamicClassCompilationStep.None;
        private int salience = 0;
        public int Salience
        {
            get
            {
                return this.salience;
            }
        }

        private bool endDetected = false;
        public bool EOR
        {
            get
            {
                return this.endDetected;
            }
        }

        private bool forceUpdate = false;
        private bool enabled = true;
        public bool Enabled
        {
            get
            {
                return this.enabled;
            }
        }

        public RuleDynamicClass(String contextClass, String ruledClass)
            : base("DynamicClass<" + contextClass + ", " + ruledClass + ">")
        {
            this.Initialize();

            this.sourceReferencedGlobal = new List<String>();

            this.entryType = ruledClass;
            this.entryContextType = contextClass;
            this.AddImport("net.veekee.common.rules");
            this.AddImport("System.Text.RegularExpressions");
            this.AddImport(this.entryType.Substring(0, this.entryType.LastIndexOf('.')));
            this.Inherited = "ARuleDynamicClass<" + this.entryContextType + ", " + this.entryType + ">";
        }

        public new void AddImport(String import)
        {
            base.AddImport(import);
        }

        public bool CheckForPreCompiledBinary()
        {
            if (File.Exists(this.GeneratedFile))
            {
                DateTime dt = File.GetLastWriteTime(this.ruleFileName);
                FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(this.GeneratedFile);
                return (fvi.LegalTrademarks.Equals(dt.ToString()));
            }
            return false;
        }

        public void Initialize()
        {
        }

        public void SetRuleFileName(String fname)
        {
            this.ruleFileName = fname;
            FileInfo fi = new FileInfo(fname);
            this.NameSpace = "net.veekee.rules." + this.CreateValidClassName(fi.Name.Substring(0, fi.Name.Length - fi.Extension.Length));
        }

        private String CreateValidClassName(String classname)
        {
            if (classname[0] == '"') classname = classname.Substring(1);
            if (classname[classname.Length - 1] == '"') classname = classname.Substring(0, classname.Length - 1);
            return new Regex(@"\W").Replace(classname, "_");
        }

        public void ProcessCommand(int lineNumber, String line, String command, String arg)
        {
            String idx = null;
            switch (command.ToLower())
            {
                case "package":
                    this.NameSpace = arg;
                    break;
                case "import":
                    this.AddImport(arg);
                    break;
                case "reference":
                    System.IO.FileInfo fi = new System.IO.FileInfo(arg);
                    if (fi.Exists)
                    {
                        try
                        {
                            Assembly a = Assembly.LoadFile(fi.FullName);
                            this.AddReference(fi.ToString(), fi.FullName, true);
                        }
                        catch (Exception e)
                        {
                            Log.GetInstance().Error("Reference file {0} load error: {1}", arg, e.ToString());
                        }
                    }
                    else
                        Log.GetInstance().Error("Reference file {0} could not be found", arg);
                    break;
                case "rule":
                    this.className = this.CreateValidClassName(arg);
                    if (!RuleDynamicClass.sourceGlobal.ContainsKey(this.ClassName))
                    {
                        RuleDynamicClass.sourceUpdated.Add(this.ClassName, false);
                        RuleDynamicClass.sourceGlobal.Add(this.ClassName, null);
                        RuleDynamicClass.sourceWhen.Add(this.ClassName, null);
                        RuleDynamicClass.sourceThen.Add(this.ClassName, null);
                    }
                    else
                    {
                        Log.GetInstance().Warn("Line {0}# Rule {1} seems to have been already processed... overwritting", lineNumber, this.className);
                    }
                    RuleDynamicClass.sourceGlobal[this.ClassName] = new List<String>();
                    RuleDynamicClass.sourceWhen[this.ClassName] = new List<String>();
                    RuleDynamicClass.sourceThen[this.ClassName] = new List<String>();
                    break;
                case "rule-end":
                case "end-rule":
                    this.endDetected = true;
                    break;
                case "end":
                    this.warnDeprecatedTag(lineNumber, "end", "rule-end");
                    this.endDetected = true;
                    break;
                case "disabled":
                    this.enabled = false;
                    break;
                case "salience":
                    Int32.TryParse(arg, out salience);
                    break;
                case "rules-filter":
                    if (arg.Length > 0)
                    {
                        List<String> filter = new List<String>(Options.GetInstance().Get("rules.filter", "").Split(new char[] { ';' }));
                        if (filter != null && !filter.Contains(arg))
                        {
                            this.Status = DynamicClassStatus.Rejected;
                        }
                    }
                    break;
                case "entry-name":
                    this.entryName = arg;
                    break;
                case "entry-context-name":
                    this.entryContextName = arg;
                    break;
                case "regex-name":
                    this.regexName = arg;
                    break;
                case "regex-expression":
                    this.regexExpression = arg;
                    break;
                case "global":
                    switch (this.compilationStep)
                    {
                        case RuleDynamicClassCompilationStep.Global:
                            this.warnSectionAlreadyOpened(lineNumber);
                            break;
                        case RuleDynamicClassCompilationStep.When:
                            this.warnSectionStillOpened(lineNumber);
                            break;
                        case RuleDynamicClassCompilationStep.Then:
                            this.warnSectionStillOpened(lineNumber);
                            break;
                    }
                    this.compilationStep = RuleDynamicClassCompilationStep.Global;
                    break;
                case "end-global":
                case "global-end":
                    if(this.compilationStep == RuleDynamicClassCompilationStep.Global)
                        this.compilationStep = RuleDynamicClassCompilationStep.None;
                    else
                        this.warnSectionNotOpened(lineNumber, "global");
                    break;
                case "use-global":
                    idx = this.CreateValidClassName(arg);
                    if (!RuleDynamicClass.sourceGlobal.ContainsKey(idx))
                    {
                        Log.GetInstance().Error("Line {0}# Referenced 'global' rule {1} could not be found", lineNumber, idx);
                        break;
                    }
                    if (this.sourceReferencedGlobal.Contains(idx))
                    {
                        Log.GetInstance().Info("Line {0}# Referenced 'global' rule {1} has already been imported", lineNumber, idx);
                        break;
                    }
                    this.sourceReferencedGlobal.Add(idx);
                    RuleDynamicClass.sourceGlobal[this.ClassName].Add("/* use-global " + idx + " */");
                    RuleDynamicClass.sourceGlobal[this.ClassName].AddRange(RuleDynamicClass.sourceGlobal[idx]);
                    this.forceUpdate |= RuleDynamicClass.sourceUpdated[idx];
                    break;
                case "when":
                    switch (this.compilationStep)
                    {
                        case RuleDynamicClassCompilationStep.Global:
                            this.warnSectionStillOpened(lineNumber);
                            break;
                        case RuleDynamicClassCompilationStep.When:
                            this.warnSectionAlreadyOpened(lineNumber);
                            break;
                        case RuleDynamicClassCompilationStep.Then:
                            this.warnSectionStillOpened(lineNumber);
                            break;
                    }
                    this.compilationStep = RuleDynamicClassCompilationStep.When;
                    break;
                case "end-when":
                case "when-end":
                    if (this.compilationStep == RuleDynamicClassCompilationStep.When)
                        this.compilationStep = RuleDynamicClassCompilationStep.None;
                    else
                        this.warnSectionNotOpened(lineNumber, "when");
                    break;
                case "when-conditional":
                    this.whenConditionalOperator = arg;
                    break;
                case "use-when":
                    idx = this.CreateValidClassName(arg);
                    if (!RuleDynamicClass.sourceWhen.ContainsKey(idx))
                    {
                        Log.GetInstance().Error("Line {0}# Referenced 'when' rule {1} could not be found", lineNumber, idx);
                        break;
                    }
                    RuleDynamicClass.sourceWhen[this.ClassName].AddRange(RuleDynamicClass.sourceWhen[idx]);
                    this.forceUpdate |= RuleDynamicClass.sourceUpdated[idx];
                    break;
                case "then":
                    switch (this.compilationStep)
                    {
                        case RuleDynamicClassCompilationStep.Global:
                            this.warnSectionStillOpened(lineNumber);
                            break;
                        case RuleDynamicClassCompilationStep.When:
                            this.warnSectionStillOpened(lineNumber);
                        break;
                        case RuleDynamicClassCompilationStep.Then:
                            this.warnSectionAlreadyOpened(lineNumber);
                            break;
                    }
                    this.compilationStep = RuleDynamicClassCompilationStep.Then;
                    break;
                case "end-then":
                case "then-end":
                    if (this.compilationStep == RuleDynamicClassCompilationStep.Then)
                        this.compilationStep = RuleDynamicClassCompilationStep.None;
                    else
                        this.warnSectionNotOpened(lineNumber, "then");
                    break;
                case "use-then":
                    idx = this.CreateValidClassName(arg);
                    if (!RuleDynamicClass.sourceThen.ContainsKey(idx))
                    {
                        Log.GetInstance().Error("Line {0}# Referenced 'then' rule {1} could not be found", lineNumber, idx);
                        break;
                    }
                    RuleDynamicClass.sourceThen[this.ClassName].AddRange(RuleDynamicClass.sourceThen[idx]);
                    this.forceUpdate |= RuleDynamicClass.sourceUpdated[idx];
                    break;
                default:
                    if (line.Length == 0)
                        return;
                    switch (this.compilationStep)
                    {
                        case RuleDynamicClassCompilationStep.Global:
                            if (!RuleDynamicClass.sourceGlobal.ContainsKey(this.className))
                            {
                                Log.GetInstance().Error("Line {0}# No rule is currently opened", lineNumber);
                                return;
                            }
                            RuleDynamicClass.sourceGlobal[this.ClassName].Add(line);
                            break;
                        case RuleDynamicClassCompilationStep.When:
                            if (!RuleDynamicClass.sourceWhen.ContainsKey(this.className))
                            {
                                Log.GetInstance().Error("Line {0}# No rule is currently opened", lineNumber);
                                return;
                            }
                            RuleDynamicClass.sourceWhen[this.ClassName].Add(line);
                            break;
                        case RuleDynamicClassCompilationStep.Then:
                            if (!RuleDynamicClass.sourceThen.ContainsKey(this.className))
                            {
                                Log.GetInstance().Error("Line {0}# No rule is currently opened", lineNumber);
                                return;
                            }
                            RuleDynamicClass.sourceThen[this.ClassName].Add(line);
                            break;
                    }
                    break;
            }
        }

        private void warnDeprecatedTag(int lineNumber, String deprecatedTag, String tag)
        {
            Log.GetInstance().Warn("{0} => Line #{1}: '{2}' tag is deprecated. Please use {2}.", this.ClassName, lineNumber, deprecatedTag, tag);
        }

        private void warnSectionAlreadyOpened(int lineNumber)
        {
            Log.GetInstance().Warn("{0} => Line #{1}: '{2}' section is already opened. It can't be opened twice.", this.ClassName, lineNumber, this.compilationStep.ToString().ToLower());
        }

        private void warnSectionStillOpened(int lineNumber)
        {
            Log.GetInstance().Warn("{0} => Line #{1}: '{2}' section is already opened. Please add '{2}-end' to remove this message.", this.ClassName, lineNumber, this.compilationStep.ToString().ToLower());
        }

        private void warnSectionNotOpened(int lineNumber, String step)
        {
            Log.GetInstance().Warn("{0} => Line #{1}: '{2}' section was not opened.", this.ClassName, lineNumber, step);
        }

        public override void Compile()
        {
            if (this.Status == DynamicClassStatus.Rejected)
            {
                Log.GetInstance().Info("Dynamic class {0} has been flaged as rejected", this.ClassName);
                return;
            }
            if (! this.Enabled)
            {
                Log.GetInstance().Info("Dynamic class {0} has been flaged as disabled", this.ClassName);
                return;
            }

            if (!this.forceUpdate && this.CheckForPreCompiledBinary())
            {
                RuleDynamicClass.sourceUpdated[this.ClassName] = false;
                this.Status = DynamicClassStatus.Valid;
                this.CompiledAssembly = Assembly.LoadFile(this.GeneratedFile);
                this.type = this.CompiledAssembly.GetType(this.NameSpace + "." + this.ClassName);
            }
            else
            {
                RuleDynamicClass.sourceUpdated[this.ClassName] = true;
                if (this.entryName == null)
                    this.entryName = "entry";
                if (this.entryContextName == null)
                    this.entryContextName = "context";
                if (this.whenConditionalOperator == null)
                    this.whenConditionalOperator = "and";
                if (this.regexName == null)
                    this.regexName = "internalRegEx";
                switch (this.whenConditionalOperator.Trim().ToLower())
                {
                    case "or":
                        this.whenConditionalOperator = "||";
                        break;
                    case "and":
                    default:
                        this.whenConditionalOperator = "&&";
                        break;
                }

                this.sourceAddOn = new StringBuilder();

                StringBuilder sbRegExp = new StringBuilder();
                String addOnRegExp = @"
        Regex _internalRegEx = {1};
        protected Match {0} = null;
        protected bool CheckCondition(String content)
        {{
            try
            {{
                if(content == null)
                {{
                    Log.GetInstance().Trace(""Cannot check pre-defined regex: null object"");
                    return false;
                }}
                if(_internalRegEx == null)
                {{
                    Log.GetInstance().Trace(""Cannot check pre-defined regex: null regex"");
                    return false;
                }}
                Log.GetInstance().Trace(""Using pre-defined regex with '{{0}}'"", content);
                this.{0} = _internalRegEx.Match(content);
                return this.{0}.Success;
            }} catch(Exception e) {{
                Log.GetInstance().Error(e.ToString());
            }}
            return false;
        }}
        protected bool CheckCondition(String regex, String content)
        {{
            try
            {{
                if(regex == null)
                {{
                    Log.GetInstance().Trace(""Cannot check pre-defined regex: null regex"");
                    return false;
                }}
                if(content == null)
                {{
                    Log.GetInstance().Trace(""Cannot check pre-defined regex: null object"");
                    return false;
                }}
                Log.GetInstance().Trace(""Using regex '{{0}}' with '{{1}}'"", regex, content);
                Regex re = new Regex(regex);
                this.{0} = re.Match(content);
                return this.{0}.Success;
            }} catch(Exception e) {{
                Log.GetInstance().Error(e.ToString());
            }}
            return false;
        }}
";
                sbRegExp.AppendLine(String.Format(addOnRegExp, this.regexName, this.regexExpression == null ? "null" : "new Regex(" + this.regexExpression + ")"));

                StringBuilder sbGlobal = new StringBuilder();
                foreach (String global in RuleDynamicClass.sourceGlobal[this.ClassName])
                {
                    sbGlobal.AppendLine(global);
                }
                String addOnGlobal = @"
                {0}

                {1}
";
                this.sourceCodeAddLine(this.sourceAddOn, String.Format(addOnGlobal, sbRegExp.ToString(), sbGlobal.ToString()));

                String separator = "";
                StringBuilder sbWhen = new StringBuilder();
                foreach (String when in RuleDynamicClass.sourceWhen[this.ClassName])
                {
                    sbWhen.Append(separator + "(" + when + ")");
                    separator = " " + this.whenConditionalOperator + " ";
                }
                if (sbWhen.Length == 0)
                    sbWhen.Append("true");
                String addOnWhen = @"
        public override bool CheckConditions({0} {1}, {2} {3})
        {{
            return {4};
        }}
";
                this.sourceCodeAddLine(this.sourceAddOn, String.Format(addOnWhen, this.entryContextType, this.entryContextName, this.entryType, this.entryName, sbWhen.ToString()));

                StringBuilder sbThen = new StringBuilder();
                foreach (String then in RuleDynamicClass.sourceThen[this.ClassName])
                {
                    sbThen.AppendLine(then);
                }
                String addOnThen = @"
        public override void Process({0} {1}, {2} {3})
        {{
            {4}
        }}

        public override DynamicClassStatus GetStatus()
        {{
            return DynamicClassStatus.{5};
        }}
";
                this.Status = (sbThen.Length == 0 ? DynamicClassStatus.Empty : DynamicClassStatus.Valid);
                this.sourceCodeAddLine(this.sourceAddOn, String.Format(addOnThen, this.entryContextType, this.entryContextName, this.entryType, this.entryName, sbThen.ToString(), this.Status.ToString()));

                this.AddAssemblyOption("AssemblyTrademark", File.GetLastWriteTime(this.ruleFileName).ToString());

                this.GenerateFileAssembly = true;
                base.Compile();
            }
        }
    }

    enum RuleDynamicClassCompilationStep
    {
        None,
        Global,
        When,
        Then
    }
}
